import namespace from "../namespace";
const PositionConstants = namespace.geo.PositionConstants;

class Point {
  constructor(x, y) {
    if (x instanceof Point) {
      this.x = x.x;
      this.y = x.y;
    } else if (!isNaN(parseFloat(x.x)) && !isNaN(parseFloat(x.y))) {
      this.x = x.x;
      this.y = x.y;
    } else {
      this.x = x;
      this.y = y;
    }

    this.bx = null;
    this.by = null;
    this.bw = null;
    this.bh = null;
  }

  setBoundary(bx, by, bw, bh) {
    if (bx instanceof namespace.geo.Rectangle) {
      this.bx = bx.x;
      this.by = bx.y;
      this.bw = bx.w;
      this.bh = bx.h;
    } else {
      this.bx = bx;
      this.by = by;
      this.bw = bw;
      this.bh = bh;
    }
    this.adjustBoundary();

    return this;
  }

  /**
   * @method
   * @private
   */
  adjustBoundary() {
    if (this.bx === null) {
      return;
    }
    this.x = Math.min(Math.max(this.bx, this.x), this.bw);
    this.y = Math.min(Math.max(this.by, this.y), this.bh);

    return this;
  }

  translate(dx, dy) {
    this.x += dx;
    this.y += dy;
    this.adjustBoundary();

    return this;
  }

  getX() {
    return this.x;
  }

  getY() {
    return this.y;
  }

  setX(x) {
    this.x = x;
    this.adjustBoundary();

    return this;
  }

  setY(y) {
    this.y = y;
    this.adjustBoundary();

    return this;
  }

  setPosition(x, y) {
    if (x instanceof Point) {
      this.x = x.x;
      this.y = x.y;
    } else {
      this.x = x;
      this.y = y;
    }
    this.adjustBoundary();

    return this;
  }

  getPosition(p) {
    let dx = p.x - this.x;
    let dy = p.y - this.y;
    if (Math.abs(dx) > Math.abs(dy)) {
      if (dx < 0) return PositionConstants.WEST;
      return PositionConstants.EAST;
    }
    if (dy < 0) return PositionConstants.NORTH;
    return PositionConstants.SOUTH;
  }

  equals(p) {
    return this.x === p.x && this.y === p.y;
  }

  distance(other) {
    return Math.sqrt(
      (this.x - other.x) * (this.x - other.x) +
      (this.y - other.y) * (this.y - other.y)
    );
  }

  getDistance(other) {
    return this.distance(other);
  }

  length() {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  }

  translated(x, y) {
    let other = new Point(x, y);
    return new Point(this.x + other.x, this.y + other.y);
  }

  scale(factor) {
    this.x *= factor;
    this.y *= factor;
    this.adjustBoundary();
    return this;
  }

  scaled(factor) {
    return new Point(this.x * factor, this.y * factor);
  }

  getScaled(factor) {
    return this.scaled(factor);
  }

  getPersistentAttributes() {
    return {
      x: this.x,
      y: this.y
    };
  }

  setPersistentAttributes(memento) {
    this.x = memento.x;
    this.y = memento.y;
  }

  subtract(that) {
    return new Point(this.x - that.x, this.y - that.y);
  }

  dot(that) {
    return this.x * that.x + this.y * that.y;
  }

  cross(that) {
    return this.x * that.y - this.y * that.x;
  }

  lerp(that, t) {
    return new Point(
      this.x + (that.x - this.x) * t,
      this.y + (that.y - this.y) * t
    );
  }

  clone() {
    return new Point(this.x, this.y);
  }
}

namespace.geo.Point = Point;
